Skip to content

build: run the declaration-import gate in strict mode; close the zod and typescript-5 declaration leaks - #586

Merged
ScriptedAlchemy merged 4 commits into
mainfrom
build/dts-strict
Sep 5, 2026
Merged

build: run the declaration-import gate in strict mode; close the zod and typescript-5 declaration leaks#586
ScriptedAlchemy merged 4 commits into
mainfrom
build/dts-strict

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Summary

pnpm lint:release now runs scripts/check-declaration-imports.mjs --strict (#568, #571) over the agent-bundle, @agent-bundle/runtime, rsc-markdown-stream, and create-agent-bundle tarballs, so a devDependency or undeclared package imported from any packed .d.ts — internal declarations included — fails the gate instead of printing a warning. The two internal leaks that made agent-bundle warn are closed at the source, and the preview docs gain the pnpm 11 blockExoticSubdeps caveat.

The two leaks

dist/events/ipc.d.tszod

export type EventRuntimeAvailability = z.infer<typeof runtimeAvailabilitySchema> derived a public type from a non-exported zod schema, so the emitter had to ship declare const runtimeAvailabilitySchema: z.ZodEnum<…> plus import { z } from 'zod'.

Fix — packages/agent-bundle/src/events/ipc.ts:76-84: the tuple is the single source; the type is derived from the tuple and the schema is built from it.

const eventRuntimeAvailabilities = ['available', 'runtime-restarted', 'runtime-unavailable'] as const;
export type EventRuntimeAvailability = (typeof eventRuntimeAvailabilities)[number];
const runtimeAvailabilitySchema = z.enum(eventRuntimeAvailabilities);

EventRuntimeAvailability resolves to the identical three-member union; the zod import stays a runtime import for wire parsing and no longer appears in the declaration.

dist/routes/input-schema.d.tstypescript-5

Eight exports of src/routes/input-schema.ts named ts.Expression / ts.Node / ts.SourceFile / ts.Statement in their signatures (ChainCall, ZodChain, unwrapExpression, positionOf, flattenZodChain, staticLiteral, scalarBaseOf, hasExportModifier). Only three of them had another importer (config-extract.ts: unwrapExpression, positionOf, hasExportModifier).

Fix:

  • New leaf packages/agent-bundle/src/routes/syntax.ts owns the three shared helpers (unwrapExpression :54, positionOf :61, hasExportModifier :67) typed against small structural interfaces (SyntaxNode, SyntaxSourceFile, SyntaxStatement, :15-28) that every ts.Node satisfies. The implementation still uses the bundled compiler (ts.SyntaxKind values), but no ts.* type reaches an exported signature, so the emitted syntax.d.ts has no imports at all. unwrapExpression is generic in the argument type (<Expression extends SyntaxNode>(e: Expression) => Expression) — exact for ts.Expression, safe for its brand-only families, documented not to be given a wrapper type.
  • input-schema.ts:6 and config-extract.ts:12 import the trio from ./syntax.ts (extract-and-rewire in one change; config-extract no longer depends on input-schema).
  • The zod-chain grammar of input-schema.ts (ChainCall :17, ZodChain :23, flattenZodChain :57, staticLiteral :77, scalarBaseOf :138) had no importer outside the module and is now module-private, so its ts.* types stop reaching the declaration. Its public surface (parseInputSchema, extractInputSchema, StaticInputSchemaProperty, ParsedInputSchema*, ScalarBase*, validationOnlyMethods) is unchanged.

stripInternal was considered and rejected: src/ already carries @internal JSDoc on public test seams (dev/epoch-store.ts, dev/workbench-server.ts, …) that must stay in the shipped declarations.

Checker output

Before (node scripts/check-declaration-imports.mjs <4 packages>):

agent-bundle: 333 packed declarations, 183 reachable from 13 export entries; 0 errors, 2 warnings
  warning dist/events/ipc.d.ts:1 imports "zod" — "zod" is a devDependency, so consumers do not install it (internal declaration; no export reaches it)
  warning dist/routes/input-schema.d.ts:1 imports "typescript-5" — "typescript-5" is a devDependency, so consumers do not install it (internal declaration; no export reaches it)
@agent-bundle/runtime: 48 packed declarations, 44 reachable from 9 export entries; 0 errors, 0 warnings
rsc-markdown-stream: 1 packed declarations, 1 reachable from 1 export entries; 0 errors, 0 warnings
create-agent-bundle: 0 packed declarations, 0 reachable from 0 export entries; 0 errors, 0 warnings

After (pnpm lint:release, i.e. --strict):

agent-bundle: 334 packed declarations, 183 reachable from 13 export entries; 0 errors, 0 warnings
@agent-bundle/runtime: 48 packed declarations, 44 reachable from 9 export entries; 0 errors, 0 warnings
rsc-markdown-stream: 1 packed declarations, 1 reachable from 1 export entries; 0 errors, 0 warnings
create-agent-bundle: 0 packed declarations, 0 reachable from 0 export entries; 0 errors, 0 warnings

(334 = 333 + the new dist/routes/syntax.d.ts.)

Gate flip and prose

  • package.json lint:release: node scripts/check-declaration-imports.mjs --strict … (one invocation covers all four packages).
  • scripts/check-declaration-imports.mjs header and packages/agent-bundle/rslib.config.ts comment now describe the strict gate. The .d.mts twin is unchanged (no API change); tests/check-declaration-imports.test.ts passes.
  • website/docs/{en,zh}/guide/distribution/preview-packages.mdx: the sentence that said lint:release does not pass --strict now says it does. docs/preview-packages.md never described the check.

Docs caveat: pnpm 11 and the rewritten preview dependency

pkg.pr.new rewrites @agent-bundle/runtimersc-markdown-stream to the renderer's same-sha preview tarball URL. pnpm 11's blockExoticSubdeps (default true, verified against https://pnpm.io/settings/dependency-resolution) rejects a transitive tarball-URL dependency with ERR_PNPM_EXOTIC_SUBDEP. One paragraph in docs/preview-packages.md and in both locales of guide/distribution/preview-packages.mdx, at the point where the rewrite is described: set blockExoticSubdeps: false in the consuming project's pnpm-workspace.yaml, or install previews with npm.

Verification

On the branch merged with origin/main (includes #575 Rslib 1.0 / Rsbuild 2.2 and #583):
pnpm install --frozen-lockfile && pnpm build, pnpm typecheck, pnpm lint, pnpm test:unit, pnpm lint:release (strict, 0 errors / 0 warnings on all four tarballs), pnpm docs:site:build (dead-link and language-parity checks pass).

Self-review

Reviewer: change-risk-reviewer subagent, model gpt-5.6-sol-medium, run against the branch (merged with origin/main) vs origin/main, asked for concrete merge risks only and specifically whether any public type changed shape. Verdict: no concrete merge risks; six areas checked, all no-risk:

  1. Public types — no exports types target re-exports events/ipc.ts, routes/input-schema.ts, or routes/syntax.ts; EventRuntimeAvailability is the same three-literal union (dist/events/ipc.d.ts:6-13). Disposition: confirmed, nothing to fix.
  2. Helper equivalencesyntax.ts peels exactly TS 5.9's five wrapper kinds (TypeAssertionExpression confirmed at typescript.d.ts:3896); direct modifiers scanning is equivalent to canHaveModifiers/getModifiers for variable/function/class statements, decorators and export default included; all unwrapExpression call sites pass expression-family types. Disposition: confirmed.
  3. Zod 4.5.4z.enum accepts const T extends readonly string[] and yields the same union; embedded strict schemas and EventRuntimeStatusResult unchanged. Disposition: confirmed.
  4. Reachabilitysyntax.ts is reached from production code through input-schema.tsroutes/graph.ts / routes/cli-argv.ts and config-extract.ts; no remaining importer of the five privatized helpers anywhere (tests included). Disposition: confirmed.
  5. GateparseArguments accepts a leading --strict; the existing test already exercises ['--strict', root]; the .d.mts API is unchanged; PR CI runs check:release:ci (.github/workflows/ci.yml:294-313), nightly/release run check:release, so the strict gate runs in CI — and did: the "Release gates" check on this PR is green. Disposition: confirmed.
  6. Docs/changeset — pnpm 11 facts verified (blockExoticSubdeps default true, pnpm-workspace.yaml, ERR_PNPM_EXOTIC_SUBDEP); en/zh parity holds; no stale --strict/lint:release prose remains; one agent-bundle: patch changeset ending in (#586). Disposition: confirmed.

No fixes resulted from the self-review, so no second reviewer pass was needed.

Codex review thread (on the pre-(#586) commit 9562558): "Add the PR number to the changeset summary" — fixed in 473f65d60, replied and resolved.

…od / typescript-5 declaration leaks

- events/ipc.ts derives EventRuntimeAvailability from a const tuple instead of
  z.infer, so dist/events/ipc.d.ts no longer imports zod.
- routes/syntax.ts owns the TypeScript-AST helpers the static route extractors
  share (unwrapExpression, positionOf, hasExportModifier) behind structural
  node types; input-schema.ts keeps its zod-chain grammar module-private, so
  dist/routes/input-schema.d.ts no longer imports typescript-5.
- lint:release passes --strict to scripts/check-declaration-imports.mjs: every
  packed declaration, internal or reachable, must resolve for a consumer.
- Preview docs: pnpm 11 blockExoticSubdeps caveat for the rewritten
  @agent-bundle/runtime -> rsc-markdown-stream preview dependency.
@changeset-bot

changeset-bot Bot commented Sep 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 591be33

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
agent-bundle Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-05T03:41:57.133022Z 9562558 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@pkg-pr-new

pkg-pr-new Bot commented Sep 5, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@586
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@586
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/rsc-markdown-stream@586
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@586

commit: 591be33

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 95625585ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .changeset/strict-declaration-imports.md Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant